<!doctype html><html class="not-ready lg:text-base" style=--bg:#faf8f1 lang=en-us><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>Thinking in Rust No.3 Send, Sync - mindbox's blog</title>
<meta name=theme-color><meta name=description content="学习Rust多线程编程，绕不开的核心概念就有Send，Sync。
我们来看Send的概念：
Types that can be transferred across thread boundaries.
Synd的概念：
Types for which it is safe to share references between threads.
这里我希望讨论几个概念，boundaries边界，共享，可变性。
边界的划定是讨论共享和可变性的前提。
// code1 fn a(){ let mut x = 100u32; b(&amp;x); c(&amp;mut x); // return &amp;x; 不可以 // return &amp;mut x; 不可以 return x; // 可以 } fn b(a:&amp;u32){} fn c(a:&amp;mut u32){} 对于x来说，a 函数就是它的边界，在边界内，我们可以共享，也可以有可变性，都可以通过&amp;T活着&amp;mut T来访问一个变量。
但是不可以返回&amp;T或者&amp;mut T，只能通过把x返回的方式，让x离开a函数这个边界，这也是一种边界的跨越。
这是一种函数（过程）边界的跨越，对于x来说，跨越边界的方式只能是把自己完整的transferred，不可以是引用的方式。
struct X{}; fn a()->X{ lat x = X{}; return x; } fn b(x:X){} fn main(){ let x = a(); b(x); } 这样对于变量x来说，就是从a函数，跨越边界到了b函数。但是当下面这种情况的时候："><meta name=author content="mindbox"><link rel="preload stylesheet" as=style href=https://mindbox.gitee.io/blog/main.min.css><link rel=preload as=image href=https://mindbox.gitee.io/blog/theme.svg><link rel=preload as=image href=https://mindbox.gitee.io/blog/rss.svg><script defer src=https://mindbox.gitee.io/blog/highlight.min.js onload=hljs.initHighlightingOnLoad()></script><link rel=icon href=https://mindbox.gitee.io/blog/favicon.ico><link rel=apple-touch-icon href=https://mindbox.gitee.io/blog/apple-touch-icon.png><meta name=generator content="Hugo 0.121.2"><meta itemprop=name content="Thinking in Rust No.3 Send, Sync"><meta itemprop=description content="学习Rust多线程编程，绕不开的核心概念就有Send，Sync。
我们来看Send的概念：
Types that can be transferred across thread boundaries.
Synd的概念：
Types for which it is safe to share references between threads.
这里我希望讨论几个概念，boundaries边界，共享，可变性。
边界的划定是讨论共享和可变性的前提。
// code1 fn a(){ let mut x = 100u32; b(&amp;x); c(&amp;mut x); // return &amp;x; 不可以 // return &amp;mut x; 不可以 return x; // 可以 } fn b(a:&amp;u32){} fn c(a:&amp;mut u32){} 对于x来说，a 函数就是它的边界，在边界内，我们可以共享，也可以有可变性，都可以通过&amp;T活着&amp;mut T来访问一个变量。
但是不可以返回&amp;T或者&amp;mut T，只能通过把x返回的方式，让x离开a函数这个边界，这也是一种边界的跨越。
这是一种函数（过程）边界的跨越，对于x来说，跨越边界的方式只能是把自己完整的transferred，不可以是引用的方式。
struct X{}; fn a()->X{ lat x = X{}; return x; } fn b(x:X){} fn main(){ let x = a(); b(x); } 这样对于变量x来说，就是从a函数，跨越边界到了b函数。但是当下面这种情况的时候："><meta itemprop=datePublished content="2023-10-31T05:23:48+08:00"><meta itemprop=dateModified content="2023-10-31T05:23:48+08:00"><meta itemprop=wordCount content="537"><meta itemprop=keywords content><meta property="og:title" content="Thinking in Rust No.3 Send, Sync"><meta property="og:description" content="学习Rust多线程编程，绕不开的核心概念就有Send，Sync。
我们来看Send的概念：
Types that can be transferred across thread boundaries.
Synd的概念：
Types for which it is safe to share references between threads.
这里我希望讨论几个概念，boundaries边界，共享，可变性。
边界的划定是讨论共享和可变性的前提。
// code1 fn a(){ let mut x = 100u32; b(&amp;x); c(&amp;mut x); // return &amp;x; 不可以 // return &amp;mut x; 不可以 return x; // 可以 } fn b(a:&amp;u32){} fn c(a:&amp;mut u32){} 对于x来说，a 函数就是它的边界，在边界内，我们可以共享，也可以有可变性，都可以通过&amp;T活着&amp;mut T来访问一个变量。
但是不可以返回&amp;T或者&amp;mut T，只能通过把x返回的方式，让x离开a函数这个边界，这也是一种边界的跨越。
这是一种函数（过程）边界的跨越，对于x来说，跨越边界的方式只能是把自己完整的transferred，不可以是引用的方式。
struct X{}; fn a()->X{ lat x = X{}; return x; } fn b(x:X){} fn main(){ let x = a(); b(x); } 这样对于变量x来说，就是从a函数，跨越边界到了b函数。但是当下面这种情况的时候："><meta property="og:type" content="article"><meta property="og:url" content="https://mindbox.gitee.io/blog/posts/thinking-in-rust-no.4/"><meta property="article:section" content="posts"><meta property="article:published_time" content="2023-10-31T05:23:48+08:00"><meta property="article:modified_time" content="2023-10-31T05:23:48+08:00"><meta name=twitter:card content="summary"><meta name=twitter:title content="Thinking in Rust No.3 Send, Sync"><meta name=twitter:description content="学习Rust多线程编程，绕不开的核心概念就有Send，Sync。
我们来看Send的概念：
Types that can be transferred across thread boundaries.
Synd的概念：
Types for which it is safe to share references between threads.
这里我希望讨论几个概念，boundaries边界，共享，可变性。
边界的划定是讨论共享和可变性的前提。
// code1 fn a(){ let mut x = 100u32; b(&amp;x); c(&amp;mut x); // return &amp;x; 不可以 // return &amp;mut x; 不可以 return x; // 可以 } fn b(a:&amp;u32){} fn c(a:&amp;mut u32){} 对于x来说，a 函数就是它的边界，在边界内，我们可以共享，也可以有可变性，都可以通过&amp;T活着&amp;mut T来访问一个变量。
但是不可以返回&amp;T或者&amp;mut T，只能通过把x返回的方式，让x离开a函数这个边界，这也是一种边界的跨越。
这是一种函数（过程）边界的跨越，对于x来说，跨越边界的方式只能是把自己完整的transferred，不可以是引用的方式。
struct X{}; fn a()->X{ lat x = X{}; return x; } fn b(x:X){} fn main(){ let x = a(); b(x); } 这样对于变量x来说，就是从a函数，跨越边界到了b函数。但是当下面这种情况的时候："><link rel=canonical href=https://mindbox.gitee.io/blog/posts/thinking-in-rust-no.4/></head><body class="text-black duration-200 ease-out dark:text-white"><header class="mx-auto flex h-[4.5rem] max-w-3xl px-8 lg:justify-center"><div class="relative z-50 mr-auto flex items-center"><a class="-translate-x-[1px] -translate-y-[1px] text-2xl font-semibold" href=https://mindbox.gitee.io/blog>mindbox's blog</a><div class="btn-dark text-[0] ml-4 h-6 w-6 shrink-0 cursor-pointer [background:url(./theme.svg)_left_center/cover_no-repeat] dark:invert dark:[background-position:right]" role=button aria-label=Dark></div></div><div class="btn-menu relative z-50 -mr-8 flex h-[4.5rem] w-[5rem] shrink-0 cursor-pointer flex-col items-center justify-center gap-2.5 lg:hidden" role=button aria-label=Menu></div><script>const htmlClass=document.documentElement.classList;setTimeout(()=>{htmlClass.remove("not-ready")},10);const btnMenu=document.querySelector(".btn-menu");btnMenu.addEventListener("click",()=>{htmlClass.toggle("open")});const metaTheme=document.querySelector('meta[name="theme-color"]'),lightBg="#faf8f1".replace(/"/g,""),setDark=e=>{metaTheme.setAttribute("content",e?"#000":lightBg),htmlClass[e?"add":"remove"]("dark"),localStorage.setItem("dark",e)},darkScheme=window.matchMedia("(prefers-color-scheme: dark)");if(htmlClass.contains("dark"))setDark(!0);else{const e=localStorage.getItem("dark");setDark(e?e==="true":darkScheme.matches)}darkScheme.addEventListener("change",e=>{setDark(e.matches)});const btnDark=document.querySelector(".btn-dark");btnDark.addEventListener("click",()=>{setDark(localStorage.getItem("dark")!=="true")})</script><div class="nav-wrapper fixed inset-x-0 top-full z-40 flex h-full select-none flex-col justify-center pb-16 duration-200 dark:bg-black lg:static lg:h-auto lg:flex-row lg:!bg-transparent lg:pb-0 lg:transition-none"><nav class="mt-12 flex justify-center space-x-10 dark:invert lg:ml-12 lg:mt-0 lg:items-center lg:space-x-6"><a class="h-8 w-8 text-[0] [background:var(--url)_center_center/cover_no-repeat] lg:h-6 lg:w-6" style=--url:url(./rss.svg) href=https://mindbox.gitee.io/blog/index.xml target=_blank rel=alternate>rss</a></nav></div></header><main class="prose prose-neutral relative mx-auto min-h-[calc(100%-9rem)] max-w-3xl px-8 pb-16 pt-12 dark:prose-invert"><article><header class=mb-16><h1 class="!my-0 pb-2.5">Thinking in Rust No.3 Send, Sync</h1><div class="text-sm antialiased opacity-60"><time>Oct 31, 2023</time></div></header><section><p>学习Rust多线程编程，绕不开的核心概念就有Send，Sync。</p><p>我们来看Send的概念：</p><blockquote><p>Types that can be transferred across thread boundaries.</p></blockquote><p>Synd的概念：</p><blockquote><p>Types for which it is safe to share references between threads.</p></blockquote><p>这里我希望讨论几个概念，boundaries边界，共享，可变性。</p><p>边界的划定是讨论共享和可变性的前提。</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#75715e>// code1
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>a</span>(){
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>let</span> <span style=color:#66d9ef>mut</span> x <span style=color:#f92672>=</span> <span style=color:#ae81ff>100</span><span style=color:#66d9ef>u32</span>;
</span></span><span style=display:flex><span>	b(<span style=color:#f92672>&amp;</span>x);
</span></span><span style=display:flex><span>	c(<span style=color:#f92672>&amp;</span><span style=color:#66d9ef>mut</span> x);
</span></span><span style=display:flex><span>	<span style=color:#75715e>// return &amp;x; 不可以
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>	<span style=color:#75715e>// return &amp;mut x; 不可以
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>	<span style=color:#66d9ef>return</span> x; <span style=color:#75715e>// 可以
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>b</span>(a:<span style=color:#66d9ef>&amp;</span><span style=color:#66d9ef>u32</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>c</span>(a:<span style=color:#66d9ef>&amp;</span><span style=color:#a6e22e>mut</span> <span style=color:#66d9ef>u32</span>){}
</span></span></code></pre></div><p>对于x来说，a 函数就是它的边界，在边界内，我们可以共享，也可以有可变性，都可以通过&amp;T活着&amp;mut T来访问一个变量。</p><p>但是不可以返回&amp;T或者&amp;mut T，只能通过把x返回的方式，让x离开a函数这个边界，这也是一种边界的跨越。</p><p>这是一种函数（过程）边界的跨越，对于x来说，跨越边界的方式只能是把自己完整的transferred，不可以是引用的方式。</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>X</span>{};
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>a</span>()-&gt;<span style=color:#a6e22e>X</span>{
</span></span><span style=display:flex><span>	lat x <span style=color:#f92672>=</span> X{};
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>return</span> x;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>b</span>(x:<span style=color:#a6e22e>X</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>main</span>(){
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> a();
</span></span><span style=display:flex><span>	b(x);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>这样对于变量x来说，就是从a函数，跨越边界到了b函数。但是当下面这种情况的时候：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>X</span>{};
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>a</span>()-&gt;<span style=color:#a6e22e>X</span>{
</span></span><span style=display:flex><span>	lat x <span style=color:#f92672>=</span> X{};
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>return</span> x;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>b</span>(x:<span style=color:#a6e22e>X</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>c</span>(x:<span style=color:#a6e22e>X</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>main</span>(){
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> a();
</span></span><span style=display:flex><span>	b(x);
</span></span><span style=display:flex><span>	<span style=color:#75715e>//c(x);//不可以
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>}
</span></span></code></pre></div><p>这里是为了模拟需要共享的情况，当b和c参数为&amp;T的话，就回到code1 的情况了，X当然也可以实现Clone，或者Copy，复制一个变量出来，一样可以让c函数运行。这里我想讨论的是共享，当复制一份变量成本很高或者根本不可能的时候，我们就需要共享变量，比如使用Rc。</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>X</span>{
</span></span><span style=display:flex><span>	big_data:[<span style=color:#66d9ef>u8</span>;<span style=color:#ae81ff>1024</span>],
</span></span><span style=display:flex><span>};
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>a</span>()-&gt;<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;</span>{
</span></span><span style=display:flex><span>	lat x <span style=color:#f92672>=</span> Rc::new(X{
</span></span><span style=display:flex><span>		big_data:[<span style=color:#ae81ff>0</span><span style=color:#66d9ef>u8</span>;<span style=color:#ae81ff>1024</span>],
</span></span><span style=display:flex><span>	});
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>return</span> x;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>b</span>(x:<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>c</span>(x:<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>main</span>(){
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> a();
</span></span><span style=display:flex><span>	b(x.clone());
</span></span><span style=display:flex><span>	c(x);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>这样就可以跨越过程边界共享变量，但是这里的x是只读的，就是共享不可变。如果我们需要修改x的内容，那么就需要用到RefCell。</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>X</span>{
</span></span><span style=display:flex><span>	big_data:[<span style=color:#66d9ef>u8</span>;<span style=color:#ae81ff>1024</span>],
</span></span><span style=display:flex><span>};
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>a</span>()-&gt;<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>RefCell<span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;&gt;</span>{
</span></span><span style=display:flex><span>	lat x <span style=color:#f92672>=</span> Rc::new(RefCell(X{
</span></span><span style=display:flex><span>		big_data:[<span style=color:#ae81ff>0</span><span style=color:#66d9ef>u8</span>;<span style=color:#ae81ff>1024</span>],
</span></span><span style=display:flex><span>	}));
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>return</span> x;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>b</span>(x:<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>RefCell<span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;&gt;</span>){
</span></span><span style=display:flex><span>	x.borrow_mut().unwrap().big_data[<span style=color:#ae81ff>0</span>] <span style=color:#f92672>=</span> <span style=color:#ae81ff>1</span>;
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>c</span>(x:<span style=color:#a6e22e>Rc</span><span style=color:#f92672>&lt;</span>RefCell<span style=color:#f92672>&lt;</span>X<span style=color:#f92672>&gt;&gt;</span>){}
</span></span><span style=display:flex><span><span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>main</span>(){
</span></span><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> a();
</span></span><span style=display:flex><span>	b(x.clone());
</span></span><span style=display:flex><span>	c(x);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>也就是说，跨越过程边界，需要传递值，跨越过程边界共享，需要Rc，跨越过程边界共享可变，需要Rc&lt;RefCell>。</p><p>这里其实是三个需求，跨越边界，共享，和可变性，当我们把跨越的边界从跨越过程，扩展到跨越线程的时候，我们就需要Send和Sync。</p><h1 id=send>Send</h1><p>先来看一下Send的定义：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>pub</span> <span style=color:#66d9ef>unsafe</span> auto <span style=color:#66d9ef>trait</span> Send {
</span></span><span style=display:flex><span>    <span style=color:#75715e>// empty.
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>}
</span></span></code></pre></div><p>marker 中一样的套路，一个空的Trait，就是一个标志位，一个开关。这里我们需要注意这个auto关键字。</p><blockquote><p>Auto traits, like Send or Sync in the standard library, are marker traits that are automatically implemented for every type, unless the type, or a type it contains, has explicitly opted out via a negative impl.</p></blockquote><p>意思就是，该Trait会为所有类型自动实现，除非该类型或者包含的类型显式的定义！Trait，这里关键的是什么类型是关闭Send的，也就是什么类型impl !Send for T。</p><p>标准库中特意关闭的类型，我罗列一些主要的：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#75715e>// core
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> <span style=color:#f92672>*</span><span style=color:#66d9ef>const</span> T {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> <span style=color:#f92672>*</span><span style=color:#66d9ef>mut</span> T {}
</span></span><span style=display:flex><span><span style=color:#75715e>// alloc
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized, A: <span style=color:#a6e22e>Allocator</span><span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> Rc<span style=color:#f92672>&lt;</span>T, A<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized, A: <span style=color:#a6e22e>Allocator</span><span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> Weak<span style=color:#f92672>&lt;</span>T, A<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#75715e>// std
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> MutexGuard<span style=color:#f92672>&lt;</span>&#39;_, T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> RwLockReadGuard<span style=color:#f92672>&lt;</span>&#39;_, T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Send <span style=color:#66d9ef>for</span> RwLockWriteGuard<span style=color:#f92672>&lt;</span>&#39;_, T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#75715e>//
</span></span></span></code></pre></div><p>这里要特别关注的是Rc，Weak是Rc伴生的类型，所以我们讨论Rc就好。</p><p>我们来看线程的入口函数：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span>    <span style=color:#66d9ef>pub</span> <span style=color:#66d9ef>fn</span> <span style=color:#a6e22e>spawn</span><span style=color:#f92672>&lt;</span>F, T<span style=color:#f92672>&gt;</span>(self, f: <span style=color:#a6e22e>F</span>) -&gt; <span style=color:#a6e22e>io</span>::Result<span style=color:#f92672>&lt;</span>JoinHandle<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;&gt;</span>
</span></span><span style=display:flex><span>    <span style=color:#66d9ef>where</span>
</span></span><span style=display:flex><span>        F: FnOnce() -&gt; <span style=color:#a6e22e>T</span>,
</span></span><span style=display:flex><span>        F: Send <span style=color:#f92672>+</span> &#39;static,
</span></span><span style=display:flex><span>        T: Send <span style=color:#f92672>+</span> &#39;static,
</span></span><span style=display:flex><span>    {
</span></span><span style=display:flex><span>        <span style=color:#66d9ef>unsafe</span> { self.spawn_unchecked(f) }
</span></span><span style=display:flex><span>    }
</span></span></code></pre></div><p>这里就是跨越线程边界的入口，可以看到参数都需要Send，所以其实Send的作用，就是阻止！Send的变量跨越线程边界，也就是不让Rc，Weak，*const T，*mut T，MutexGuard等这些类型跨越边界。所以费这么半天劲，就是为了阻止这么几个类型穿越边界罢了。</p><p>也就是跨越边界的类型，只要不包含这些类型，都可以跨越线程边界，这基本包含所有的类型。</p><p>这里的&rsquo;static 可以简单理解为只能传值，不能传递引用，&amp;T，&amp;mut T。（完整的概念是只能传递静态生命周期的值，那又是一个很长的故事，留坑以后填）</p><h1 id=sync>Sync</h1><p>我们来看Sync的定义：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>pub</span> <span style=color:#66d9ef>unsafe</span> auto <span style=color:#66d9ef>trait</span> Sync {
</span></span><span style=display:flex><span>    <span style=color:#75715e>// Empty
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>}
</span></span></code></pre></div><p>一样的概念，我们来看哪些类型定义了！Sync：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#75715e>// core
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> <span style=color:#f92672>*</span><span style=color:#66d9ef>const</span> T {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> <span style=color:#f92672>*</span><span style=color:#66d9ef>mut</span> T {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> Cell<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> RefCell<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> UnsafeCell<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> OnceCell<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#75715e>// alloc
</span></span></span><span style=display:flex><span><span style=color:#75715e>// Note that this negative impl isn&#39;t strictly necessary for correctness,
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized, A: <span style=color:#a6e22e>Allocator</span><span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> Rc<span style=color:#f92672>&lt;</span>T, A<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized, A: <span style=color:#a6e22e>Allocator</span><span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> Weak<span style=color:#f92672>&lt;</span>T, A<span style=color:#f92672>&gt;</span> {}
</span></span><span style=display:flex><span><span style=color:#75715e>// std
</span></span></span><span style=display:flex><span><span style=color:#75715e>// std::sync::mpsc
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> <span style=color:#f92672>!</span>Sync <span style=color:#66d9ef>for</span> Receiver<span style=color:#f92672>&lt;</span>T<span style=color:#f92672>&gt;</span> {}
</span></span></code></pre></div><p>简单来说，Sync的目的就是为了阻止Cell的跨线程共享。</p><h1 id=共享可变>共享，可变</h1><p>从最底层的&amp;T，&amp;mut T，甚至更基础的，let a：T，let mut a:T,Rust 一直在管理的都是变量的共享和可变性。当变量需要跨越边界的时候，Rust提供了各种工具来达成跨越边界，并且不破坏共享不可变，可变不共享原则，而Send和Sync，是为了跨越线程这个边界而特意设定的工具。</p><p>我们来总结一下各个边界情况的工具和类型。</p><ol><li><p>过程（函数）边界内</p><ul><li>共享，不可写：&amp;T</li><li>不共享，可写：T,&amp;mut T</li><li>共享，可写：&amp;RefCell,&amp;Cell</li></ul></li><li><p>跨越过程边界</p><ul><li>共享，不可写：Rc,</li><li>不共享，可写: T,</li><li>共享，可写：Rc&lt;RefCell>,Rc&lt;Cell>,</li></ul></li><li><p>跨越线程边界</p><ul><li>共享，不可写：Arc,</li><li>不共享，可写：T,</li><li>共享，可写：Arc&lt;Mutex></li></ul></li></ol><p>然后汇总一下：</p><ol><li>对于一个不包含共享内容的变量来说，所有权转移，可以跨越任何边界，跨越过去后可写不可写都可以。</li><li>引用类型只是过程边界内有效的传递机制，要跳出过程边界必须要移交所有权。</li><li>Rc，Arc都是为了解决共享的问题，与可变性没有关系。</li><li>Cell，RefCell,Mutex等是为了解决可写性问题，与共享与否没有关系。</li></ol><p>共享和可变性是一组正交的概念，Send和Sync也是一组正交的概念，Send标志是为了解决共享问题，阻止非法的共享，Sync标志是为了解决可变性问题，阻止非法的可变性。</p><p>那么就会有下面四种情况：</p><ol><li>Send + Sync 不包含共享可变性工具的数据结构都是这种，大部分时候我们定义的数据结构都满足。</li><li>!Send + !Sync 比如Rc&lt;RefCell>,单线程数据结构，但是需要共享和可变性，只能在单个线程中使用。</li><li>Send + !Sync 下面单独讨论。</li><li>!Send + Sync 比如Rc&lt;Mutex>,这种是没有意义的，可以直接用Rc&lt;RefCell>来代替。</li></ol><h1 id=send--sync>Send + !Sync</h1><p>先来看一段代码：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> RefCell::new(<span style=color:#ae81ff>100</span>);
</span></span><span style=display:flex><span>	thread::spawn(<span style=color:#66d9ef>move</span> <span style=color:#f92672>||</span> {
</span></span><span style=display:flex><span>		<span style=color:#f92672>*</span>x.borrow_mut() <span style=color:#f92672>=</span> <span style=color:#ae81ff>200</span>;
</span></span><span style=display:flex><span>	});
</span></span></code></pre></div><p>x 是一个Send + !Sync 的变量，但是可以安全的穿越线程边界，因为thread::spawn 的函数签名中只要求Send，并没有要求Sync。再看这个：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span>	<span style=color:#66d9ef>let</span> x <span style=color:#f92672>=</span> Arc::new(RefCell::new(<span style=color:#ae81ff>100</span>));
</span></span><span style=display:flex><span>	thread::spawn(<span style=color:#66d9ef>move</span> <span style=color:#f92672>||</span> {     		<span style=color:#75715e>// 不可以
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>		<span style=color:#f92672>*</span>x.borrow_mut() <span style=color:#f92672>=</span> <span style=color:#ae81ff>200</span>;
</span></span><span style=display:flex><span>	});
</span></span></code></pre></div><p>这样就不可以了，原因在Arc的代码中：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>unsafe</span> <span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: <span style=color:#f92672>?</span>Sized <span style=color:#f92672>+</span> Sync <span style=color:#f92672>+</span> Send, A: <span style=color:#a6e22e>Allocator</span> <span style=color:#f92672>+</span> Send<span style=color:#f92672>&gt;</span> Send <span style=color:#66d9ef>for</span> Arc<span style=color:#f92672>&lt;</span>T, A<span style=color:#f92672>&gt;</span> {}
</span></span></code></pre></div><p>对于Arc来说，只有内部的值Sync的时候，Arc才会有Send，否则Arc是没有Send的，那么也就不可以跨越线程的边界。</p><p>其实thread::spawn做的事情就是，你给我的参数有没有共享数据，没有共享那OK你随意，如果有共享数据，那么你共享的数据是不是只读的，是的，那么OK你随意，如果不是，那么你的共享数据有没有用Mutex这类同步元语包住来保证线程安全，如果有，那么OK你随意，如果没有，那对不起，不行。</p><p>其实Send和Trait就是两个空的Trait，在运行时它们根本就不存在，零成本，所有的线程安全都是这些概念的组合一起完成的，就是把可能犯错的缺口全部堵死，以此来保证安全性。</p><p>语言本身就是检查一个一个的空Trait标签，函数要求你impl这个Trait，你没有impl，那么就报错，并没有额外的魔法。</p><p>一切魔法都是抽象出正交的一系列概念，然后通过这些正交概念的组合来完成的。</p><h1 id=t-mut-t>&amp;T &amp;mut T</h1><p>在core::marker中，有这样一行代码：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-rust data-lang=rust><span style=display:flex><span><span style=color:#66d9ef>unsafe</span> <span style=color:#66d9ef>impl</span><span style=color:#f92672>&lt;</span>T: Sync <span style=color:#f92672>+</span> <span style=color:#f92672>?</span>Sized<span style=color:#f92672>&gt;</span> Send <span style=color:#66d9ef>for</span> <span style=color:#f92672>&amp;</span>T {}
</span></span></code></pre></div><p>是的，对于拥有Sync的类型来说，它的&amp;T时Send的，而且，在Sync的文档中，也特意提到 &amp;T，&amp;mut T，如果T：Sync，那么这两个类型也都是Sync的。</p><p>抛开这些复杂的逻辑，这里核心的问题是，thread::spawn的那个&rsquo;static。如果参数是一个闭包的情况，在堆栈上的所有变量，生命周期都是跟着函数过程的，要满足&rsquo;static这个生命周期基本就是代表你把值全转移给我，不能是引用。符合这个条件的引用类型基本就只剩下静态函数。</p><p>特意提到这个是因为光看文档会特别绕，特别是引用的Send和Sync，其实加上&rsquo;static后，thread::spawn的参数基本就全卡死了，不用unsafe根本绕不过。</p><h1 id=总结>总结</h1><p>文章肯定还有很多错误，我能写的只能是我现在所能理解的理解。</p><p>我想表达的是，其实没什么魔法，一切都在标准库的源代码里面，零成本抽象，正交的概念，通过组合来实现目的。</p><p>下一期想聊marker里面的最后一个概念，Unpin，一个特别难理解，但是源代码好简单的东西。</p></section></article></main><footer class="opaco mx-auto flex h-[4.5rem] max-w-3xl items-center px-8 text-[0.9em] opacity-60"><div class=mr-auto>&copy; 2024
<a class=link href=https://mindbox.gitee.io/blog>mindbox's blog</a></div><a class="link mx-6" href=https://gohugo.io/ rel=noopener target=_blank>Powered by Hugo️️</a>️
<a class=link href=https://github.com/nanxiaobei/hugo-paper rel=noopener target=_blank>✎ Paper</a></footer></body></html>